Skip to main content

TypeORM

This wiki guide shows you how to work with TypeORM in CellularJS:

1. Installation#

yarn add @cellularjs/typeorm typeorm

Note, you need to install database driver too. Eg: PostgreSQL.

yarn add pg
caution

@cellularjs/typeorm is built on top of TypeORM. Currently, this package is only tested and guaranteed to work with PostgreSQL. Other databases, even RDMS(eg: SQLite), may not work as expected.

2. DataSource and entity#

Start by initializing your data source, usually within the common module for centralized access. You can create multiple data sources, each with a unique name.

// common.module.tsimport { Module } from '@cellularjs/di';import { TypeOrmModule } from '@cellularjs/typeorm';
@Module({  exports: [    TypeOrmModule.initialize({      type: 'postgres',      ...    }),    TypeOrmModule.initialize({      name: 'pg_2',      type: 'postgres',      ...    }),  ]})export class CommonModule {}

With your data source ready, use TypeOrmModule.forFeature to register your entities with it.

import { Cell } from '@cellularjs/net';import { TypeOrmModule } from '@cellularjs/typeorm';import { CommonModule } from './common.module';
@Cell({  imports: [    CommonModule,    // These below entities will be available for using in UserCell.    TypeOrmModule.forFeature({      // source: '...',      entities: [UserEntity, UserRegistrationEntity]    }),  ],  providers: ['./'],  listen: './',})export class UserCell {}

You can get/remove initialized data source with getDataSource and destroyDataSource.

// somewhere_on_earchimport { getDataSource, destroyDataSource } from '@cellularjs/typeorm';
getDataSource();destroyDataSource(); // close connection and remove datasource.

3. Repository#

Repository decorator help you working with TypeORM's Data Mapper pattern more easily. Under the hood, it utilizes DI proxy to facilitate the creation of TypeORM's repositories.

note

The difference between the repository pattern that I know and TypeORM's repository is that you are the owner of API interfaces. TypeORM's repository is not something like that, and actually, it's usually not worth owning a repository interface. From now on, the term "repository" used here refers to TypeORM's repository.

Example 1: defining entity, repository, and using it.

// user.dal.tsimport { Entity, PrimaryGeneratedColumn, Column, Repository as TypeOrmRepository } from 'typeorm';import { Repository } from '@cellularjs/typeorm';
@Entity()class UserEntity {@PrimaryGeneratedColumn('uuid')  id: string;
@Column()  name: string;}
export interface UserRepository extends TypeOrmRepository<UserEntity> { }
@Repository({ entity: UserEntity })export class UserRepository { }
// user-info.qry.tsimport { Service, ServiceHandler } from '@cellularjs/net';import { UserRepository } from './user.dal';
@Service({ scope: 'publish' })export class UserInfoQry implements ServiceHandler {  constructor(    private userRepository: UserRepository,  ) {}
  async handle() {    // this.userReposotiry.find(...)  }}

Example 2: add custom method to repository.

// user.dal.ts...export interface UserRepository extends TypeOrmRepository<UserEntity> { }
@Repository({ entity: UserEntity })export class UserRepository {  async findByName(name: string) {    return this.findOneBy({ name });  }}

4. Transactional#

4.1. Basic usage#

The Transactional decorator is intended for a single transaction per request(or service handler). It works by replacing the repository's query runner during dependency resolution. The query runner that is internally used by the repository is now getting from Transactional

tip

To optimize performance, avoid using the Transactional decorator for service that is dedicated to data retrieval(Eg: fetching user information, ...).

Example 1: Using Repository and Transactional decorator.

// rename.cmd.tsimport { Transactional } from '@cellularjs/typeorm';import { UserRepository } from './user.dal';
@Transactional()@Service({ scope: 'publish' })export class RenameCmd implements ServiceHandler {  constructor(    private userRepository: UserRepository,  ) {}
  async handle() {    const user = await this.userRepository.findOneBy({ id: 1 })    user.name = 'X'
    await this.userRepository.save(user);  }}

4.2. Error handling#

4.2.1. setDefaultCatch#

You can use setDefaultCatch to set default error handler for transactions errors.

Example:

// somewhere_on_earth.tsimport { Transactional, TransactionalCatch, setDefaultCatch } from '@cellularjs/typeorm';
const MAX_RETRY = 5;
const txCatch: TransactionalCatch = async ({ error, service, ctx }) => {  // Ref: https://www.postgresql.org/docs/current/mvcc-serialization-failure-handling.html  if (error?.code !== '40001') throw error;
  ctx.try ||= 0;  ctx.try++;
  if (ctx.try > MAX_RETRY) throw error;
  return await service.handle();}
setDefaultCatch(txCatch);
@Transactional()@Service({ scope: 'publish' })export class ServiceName {   ...}

4.2.2. catch#

For customizing the error handler in a specific service, use catch property of Transactional.

// somewhere_in_the_universe.ts@Transactional({ catch: txCatch })@Service({ scope: 'publish' })export class ServiceName {   ...}

4.3. Get current QueryRunner#

You can access query runner instance that is created by Transactional decorator.

Example:

import { Transactional, QueryRunner } from '@cellularjs/typeorm';
@Transactional()@Service({ scope: 'publish' })export class ServiceName {  constructor(    private queryRunner: QueryRunner,  ) { }
  async handle() {    await this.queryRunner.query('SELECT pg_advisory_xact_lock(9999)');  }  ...}